classdef C_channel < handle
    %C_CHANNEL Summary of this class goes here
    %   Detailed explanation goes here
    
    properties (GetAccess = {?C_Eval, ?C_Rx}, SetAccess='private')
        Param;
        RayFiltData;
        EsPerNo;
    end
    
    properties (GetAccess='private', SetAccess='private')
        SigPwr;
        
        RayChanObj;
        TxObj;       
    end

    methods (Access = private)
        function this = C_channel(TxObj)
            this.RayChanObj = rayleighchan();
            this.TxObj = TxObj;
        end
    end
    
    methods (Static)
        function this = Instance(TxObj)
             persistent this_obj;
             
             if isempty(this_obj) || ~isvalid(this_obj)
                this_obj = C_channel(TxObj);
             end
             
             this = this_obj;            
        end          
        
        function Initialise(this, param)
            this.Param = param;
            
            this.RayChanObj.InputSamplePeriod = 1/this.TxObj.Param.Fs;
            this.RayChanObj.MaxDopplerShift = this.Param.FdMax;
            this.RayChanObj.PathDelays = this.Param.PathDelays;
            this.RayChanObj.AvgPathGaindB = this.Param.AvgPathGaindB;
            this.RayChanObj.NormalizePathGains = 1;
            this.RayChanObj.StoreHistory = 0;%any(this.Param.FdMax);
            this.RayChanObj.StorePathGains = 1;
            this.RayChanObj.ResetBeforeFiltering = 1; 
        end
        
        function SigOut = AddChannelResponse(this, SigIn)
            SigOut = SigIn;
            measSigPwr(this, SigIn);
            SigOut = addRayleigh(this, SigOut);
            SigOut = addWhiteNoise(this, SigOut);
        end
    end
end

function SigOut = addRayleigh(this, SigIn)
    for ofdmBlockIndex = 1:size(SigIn,2)
        SigOut(:,ofdmBlockIndex) = transpose(filter(this.RayChanObj, transpose(SigIn(:,ofdmBlockIndex))));
        this.RayFiltData.PathGain(:,ofdmBlockIndex,:) = permute(this.RayChanObj.PathGains, [1 3 2]);
        this.RayFiltData.AlphaMatrix(:,ofdmBlockIndex,:) =  permute(this.RayChanObj.channelFilter.alphaMatrix.', [1 3 2]);
    end
end

% function SigOut = addWhiteNoise(this, SigIn)
%     SigIn = reshape(SigIn, 1, []);
%     EsPerNo = this.Param.EbPerNo...
%          + 10*log10( 1 / (1 + this.TxObj.Param.GI_sizePct/100) )...
%          + 10*log10(length(this.TxObj.Param.ActiveModBand)/(this.TxObj.Param.numSubcarriers))...
%          + 10*log10(log2(this.TxObj.M));
% 
% %     if (isreal(SigIn) == 1)
% %         SNR = EsPerNo - log10(( 0.5 * this.TxObj.Param.numSubcarriers*2 / this.TxObj.sigSize(1)));
% %     else
% %         SNR = EsPerNo - log10(( this.TxObj.Param.numSubcarriers*2 / this.TxObj.sigSize(1)));
% %     end
% 
%     SNR = EsPerNo;
%     
%     for SNRindex=1:length(this.Param.EbPerNo)
%         noisySig = awgn( SigIn, SNR(SNRindex), 10*log10(this.SigPwr));
%         SigOut(:,:,SNRindex) = reshape(noisySig, this.TxObj.sigSize(1), this.TxObj.sigSize(2));               
%     end
% end

function SigOut = addWhiteNoise(this, SigIn)
    SigIn = reshape(SigIn, 1, []);
    this.EsPerNo = 10.^(this.Param.EbPerNo/10)...
         * ( 1 / (1 + this.TxObj.Param.GI_sizePct/100) )...
         * ( (length(this.TxObj.Param.ActiveModBand) + length(this.TxObj.Param.PilotModBand)) / (this.TxObj.Param.numSubcarriers))...
         * log2(this.TxObj.M);    
     
    NoisePwr = this.SigPwr ./ this.EsPerNo;
    RealNoise = randn(1, length(SigIn));
    ImagNoise = randn(1, length(SigIn));
    Noise = complex(RealNoise, ImagNoise);
    SigOut = reshape((repmat(SigIn, length(NoisePwr), 1) + sqrt(NoisePwr/2)' * Noise).', this.TxObj.sigSize(1), this.TxObj.sigSize(2), []);
end

function measSigPwr(this, SigIn)
    SigIn = reshape(SigIn, 1, []);
    this.SigPwr = sum(SigIn.^2)/length(SigIn);
end

